package org.gdstash.db;

import org.gdstash.file.ARCDecompress;
import org.gdstash.file.ARZRecord;
import org.gdstash.ui.GDStashFrame;
import org.gdstash.util.GDColor;
import org.gdstash.util.GDConstants;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class DBItemSet {
    private static final String TABLE_NAME = "GD_ITEMSET";
    private static final int ROW_ITEMSET_ID = 1;
    private static final int ROW_NAME = 2;
    private static final int ROW_DESCRIPTION = 3;
    private static final int ROW_SKILL_MOD_LEVEL = 4;
    private String itemSetID;
    private String name;
    private String description;
    private int skillModifierLevel;
    private List<String> itemIDs;
    private String rarity;
    private int reqLevel;
    private List<DBStat> stats;
    private List<DBSkillBonus> bonuses;
    private List<DBSkillModifier> skillModifiers;

    public DBItemSet() {
        this.itemIDs = new LinkedList<String>();
        this.stats = new LinkedList<DBStat>();
        this.bonuses = new LinkedList<DBSkillBonus>();
        this.skillModifiers = new LinkedList<DBSkillModifier>();
    }

    public DBItemSet(final String name) {
        this();
        this.name = name;
    }

    public DBItemSet(final ARZRecord record) {
        this();
        this.itemSetID = record.getFileName();
        this.setName(record.getItemSetNameTag());
        this.setDescription(record.getItemSetDescriptionTag());
        this.itemIDs = record.getItemSetItemIDList();
        this.skillModifierLevel = record.getItemSetSkillModifierLevel();
        this.rarity = record.getRarity();
        this.reqLevel = record.getRequiredLevel();
        this.stats = record.getDBStatList();
        this.bonuses = record.dbSkillBonuses;
        this.skillModifiers = record.getSkillModifierList();
    }

    private static List<DBItemSet> wrap(final ResultSet rs) throws SQLException {
        final LinkedList<DBItemSet> list = new LinkedList<DBItemSet>();
        while (rs.next()) {
            final DBItemSet set = new DBItemSet();
            set.itemSetID = rs.getString(1);
            set.name = rs.getString(2);
            set.description = rs.getString(3);
            set.skillModifierLevel = rs.getInt(4);
            set.itemIDs = DBItemSetAlloc.getByItemSetID(set.itemSetID);
            set.stats = DBStat.getItemSet(set.itemSetID);
            set.bonuses = DBSkillBonus.getItemSet(set.itemSetID);
            set.skillModifiers = DBSkillModifier.getItemSet(set.itemSetID);
            String rarity = null;
            boolean matchRarity = true;
            int reqLevel = 0;
            boolean matchLevel = true;
            if (set.itemIDs != null) {
                final List<DBItem.SetInfo> infos = DBItem.getSetInfoByItemIDs(set.itemIDs);
                for (final DBItem.SetInfo info : infos) {
                    if (info == null) {
                        continue;
                    }
                    if (rarity == null) {
                        rarity = info.rarity;
                    } else {
                        matchRarity = (matchRarity && rarity.equals(info.rarity));
                    }
                    if (reqLevel == 0) {
                        reqLevel = info.level;
                    } else {
                        matchLevel = (reqLevel == info.level);
                    }
                }
                if (matchRarity) {
                    set.rarity = rarity;
                }
                if (matchLevel) {
                    set.reqLevel = reqLevel;
                }
            }
            if (set.name != null) {
                final ByteBuffer buffer = StandardCharsets.UTF_8.encode(set.name);
                final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                set.name = utf8;
            }
            if (set.description != null) {
                final ByteBuffer buffer = StandardCharsets.UTF_8.encode(set.description);
                final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                set.description = utf8;
            }
            list.add(set);
        }
        return list;
    }

    public String getItemSetID() {
        return this.itemSetID;
    }

    public String getName() {
        return this.name;
    }

    private void setName(final String name) {
        this.name = GDStashFrame.arcList.getTag(ARCDecompress.FileModule.All, GDConstants.TAG_TEXT_ITEMS, name, false);
    }

    public String getDescription() {
        return this.description;
    }

    private void setDescription(final String description) {
        this.description = GDStashFrame.arcList.getTag(ARCDecompress.FileModule.All, GDConstants.TAG_TEXT_ITEMS, description, true);
    }

    public List<String> getItemIDList() {
        return this.itemIDs;
    }

    public static void createTables() throws SQLException {
        final String dropTable = "DROP TABLE GD_ITEMSET";
        final String createTable = "CREATE TABLE GD_ITEMSET (ITEMSET_ID      VARCHAR(256) NOT NULL, NAME            VARCHAR(256), DESCRIPTION     VARCHAR(1024), SKILL_MOD_LEVEL INT, PRIMARY KEY (ITEMSET_ID))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                if (GDDBUtil.tableExists(conn, "GD_ITEMSET")) {
                    st.execute(dropTable);
                }
                st.execute(createTable);
                st.close();
                conn.commit();
                DBItemSetAlloc.createTable(conn);
                DBStat.createItemSetTable(conn);
                DBSkillBonus.createItemSetTable(conn);
                DBSkillModifier.createItemSetTable(conn);
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {"GD_ITEMSET"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
                GDMsgLogger.addError(msg);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static void delete(final String itemSetID) throws SQLException {
        final String deleteEntry = "DELETE FROM GD_ITEMSET WHERE ITEMSET_ID = ?";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(deleteEntry)) {
                ps.setString(1, itemSetID);
                ps.executeUpdate();
                ps.close();
                DBItemSetAlloc.delete(conn, itemSetID);
                DBStat.deleteItemSet(conn, itemSetID);
                DBSkillBonus.deleteItemSet(conn, itemSetID);
                DBSkillModifier.deleteItemSet(conn, itemSetID);
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {itemSetID, "GD_ITEMSET"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_DEL_TABLE_BY_ID", args);
                GDMsgLogger.addError(msg);
                GDMsgLogger.addError(ex);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        } catch (SQLException ex2) {
            throw ex2;
        }
    }

    public static void insert(final ARZRecord record) throws SQLException {
        final DBItemSet entry = get(record.getFileName());
        if (entry != null) {
            return;
        }
        final DBItemSet itemSet = new DBItemSet(record);
        if (itemSet.itemIDs == null) {
            return;
        }
        if (itemSet.itemIDs.isEmpty()) {
            return;
        }
        final String insert = "INSERT INTO GD_ITEMSET VALUES (?,?,?,?)";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(insert)) {
                ps.setString(1, itemSet.itemSetID);
                ps.setString(2, itemSet.name);
                ps.setString(3, itemSet.description);
                ps.setInt(4, itemSet.skillModifierLevel);
                ps.executeUpdate();
                ps.close();
                DBItemSetAlloc.insert(conn, itemSet);
                DBStat.insertItemSet(conn, itemSet.itemSetID, itemSet.stats);
                DBSkillBonus.insertItemSet(conn, itemSet.itemSetID, itemSet.bonuses);
                DBSkillModifier.insertItemSet(conn, itemSet.itemSetID, itemSet.skillModifiers);
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {record.getFileName(), "GD_ITEMSET"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_INS_TABLE_BY_ID", args);
                GDMsgLogger.addLowError(msg);
                GDMsgLogger.addLowError(ex);
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static DBItemSet get(final String itemSetID) {
        DBItemSet set = new DBItemSet();
        final String command = "SELECT * FROM GD_ITEMSET WHERE ITEMSET_ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, itemSetID);
            try (final ResultSet rs = ps.executeQuery()) {
                final List<DBItemSet> list = wrap(rs);
                if (list.isEmpty()) {
                    set = null;
                } else {
                    set = list.get(0);
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {itemSetID, "GD_ITEMSET"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return set;
    }

    public static List<DBItemSet> getAll() {
        List<DBItemSet> list = null;
        final String command = "SELECT * FROM GD_ITEMSET ORDER BY NAME";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            try (final ResultSet rs = ps.executeQuery()) {
                list = wrap(rs);
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_READ_ITEMSET_ALL"));
            GDMsgLogger.addError(ex2);
        }
        return list;
    }

    public List<DBSkillModifier> getSkillModifiers(final int level) {
        if (level == this.skillModifierLevel) {
            return this.skillModifiers;
        }
        return null;
    }

    public ArrayList<List<DBStat>> getBonusesPerLevel() {
        if (this.itemIDs == null) {
            return null;
        }
        if (this.itemIDs.size() == 0) {
            return null;
        }
        final ArrayList<List<DBStat>> arrStat = new ArrayList<List<DBStat>>(this.itemIDs.size());
        for (int i = 0; i < this.itemIDs.size(); ++i) {
            arrStat.add(DBStat.getStatsForExactLevel(this.stats, i + 1));
        }
        compactBonuses(arrStat);
        return arrStat;
    }

    private static void compactBonuses(final ArrayList<List<DBStat>> arrStat) {
        for (int i = 0; i < arrStat.size(); ++i) {
            final Iterator<DBStat> iter = arrStat.get(i).iterator();
            while (iter.hasNext()) {
                final DBStat stat1 = iter.next();
                for (int j = 0; j < i; ++j) {
                    for (final DBStat stat2 : arrStat.get(j)) {
                        if (stat1.getStatType().equals(stat2.getStatType())) {
                            iter.remove();
                        }
                    }
                }
            }
        }
    }

    public ArrayList<List<DBSkillBonus>> getItemSetSkillBonusesPerLevel() {
        if (this.itemIDs == null) {
            return null;
        }
        if (this.itemIDs.isEmpty()) {
            return null;
        }
        final ArrayList<List<DBSkillBonus>> arrBonus = new ArrayList<List<DBSkillBonus>>(this.itemIDs.size());
        for (int i = 0; i < this.itemIDs.size(); ++i) {
            arrBonus.add(new LinkedList<DBSkillBonus>());
            for (DBSkillBonus bonus : this.bonuses) {
                final int value = bonus.getValueForLevel(i);
                if (value > 0) {
                    bonus = (DBSkillBonus) bonus.clone();
                    bonus.setValue(value);
                    arrBonus.get(i).add(bonus);
                }
            }
        }
        compactSkillBonuses(arrBonus);
        return arrBonus;
    }

    private static void compactSkillBonuses(final ArrayList<List<DBSkillBonus>> arrBonus) {
        for (int i = 0; i < arrBonus.size(); ++i) {
            final Iterator<DBSkillBonus> iter = arrBonus.get(i).iterator();
            while (iter.hasNext()) {
                final DBSkillBonus bonus1 = iter.next();
                for (int j = 0; j < i; ++j) {
                    for (final DBSkillBonus bonus2 : arrBonus.get(j)) {
                        if (bonus1.getEntity() == null) {
                            if (bonus2.getEntity() != null) {
                                continue;
                            }
                            if (bonus1.getValue() == bonus2.getValue()) {
                                iter.remove();
                                break;
                            }
                            break;
                        } else {
                            if (!bonus1.getEntity().equals(bonus2.getEntity())) {
                                continue;
                            }
                            if (bonus1.getValue() == bonus2.getValue()) {
                                iter.remove();
                                break;
                            }
                            break;
                        }
                    }
                }
            }
        }
    }

    @Override
    public String toString() {
        String s = this.name;
        if (this.reqLevel != 0) {
            final Object[] args = {String.format("%03d", this.reqLevel)};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbGD, "TXT_LEVEL_NUM", args);
            s = s + " [" + msg + "]";
        }
        if (this.rarity != null) {
            if (this.rarity.equals("Epic")) {
                s = "<html>" + GDColor.HTML_COLOR_EPIC + s + "</font>" + "</html>";
            }
            if (this.rarity.equals("Legendary")) {
                s = "<html>" + GDColor.HTML_COLOR_LEGENDARY + s + "</font>" + "</html>";
            }
        }
        return s;
    }
}
